/*-----------------------------------------------------------------------------
	The contents of this file are subject to the Mozilla Public License
	Version 1.1 (the "License"); you may not use this file except in
	compliance with the License. You may obtain a copy of the License at
	http://www.mozilla.org/MPL/
	
	Software distributed under the License is distributed on an "AS IS"
	basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
	License for the specific language governing rights and limitations
	under the License.
	
	The Original Code is Fever Framework code.
	
	The Initial Developer of the Original Code is Romain Ecarnot.
	Portions created by Initial Developer are Copyright (C) 2006
	the Initial Developer. All Rights Reserved.
	
	Contributor(s):
	
	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
-----------------------------------------------------------------------------*/

import org.aswing.ASFont;
import org.aswing.border.EmptyBorder;
import org.aswing.BorderLayout;
import org.aswing.Component;
import org.aswing.dnd.DragListener;
import org.aswing.dnd.DragManager;
import org.aswing.dnd.RejectedMotion;
import org.aswing.dnd.SourceData;
import org.aswing.GeneralListCellFactory;
import org.aswing.geom.Point;
import org.aswing.Insets;
import org.aswing.JFrame;
import org.aswing.JLabel;
import org.aswing.JListTree;
import org.aswing.JMenuBar;
import org.aswing.JMenuItem;
import org.aswing.JPanel;
import org.aswing.JScrollPane;
import org.aswing.JSeparator;
import org.aswing.SoftBoxLayout;
import org.aswing.tree.DefaultMutableTreeNode;
import org.aswing.tree.DefaultTreeModel;
import org.aswing.tree.list.DefaultListTreeCell;
import org.aswing.tree.MutableTreeNode;
import org.aswing.tree.TreeCell;
import org.aswing.tree.TreePath;

import com.bourre.commands.Delegate;
import com.bourre.core.HashCodeFactory;
import com.bourre.data.collections.Map;
import com.bourre.data.iterator.Iterator;
import com.bourre.events.BasicEvent;
import com.bourre.events.EventBroadcaster;
import com.bourre.events.EventType;
import com.bourre.utils.ClassUtils;

import fever.app.accelerator.Keyboard;
import fever.app.accelerator.KeyCombo;
import fever.app.accelerator.KeyDefinition;
import fever.app.accelerator.ShortcutLocator;
import fever.app.accelerator.ShortcutMap;
import fever.app.local.Localisation;
import fever.app.local.LocalisationEvent;
import fever.app.local.LocalisationListener;
import fever.Fever;
import fever.log.FeverDebug;
import fever.utils.Stringifier;

import fvaswing.components.cursors.FvCopyCursor;
import fvaswing.components.menu.FvMenu;
import fvaswing.components.menu.FvMenuItem;
import fvaswing.components.reader.FvReaderResources;
import fvaswing.components.reader.playlist.FvPlayListItem;
import fvaswing.components.reader.playlist.FvPlayListNode;
import fvaswing.FvAsWing;

/**
 * Playlist base component.
 * 
 * <p>Can be popup throw JFrame or embeded into other component using 
 * {@link #getView()} method.
 * 
 * <p>{@link fever.app.local.Localisation} is connected for translation.
 * 
 * <p>Load / save operation used argument {@code name} in constructor call to 
 * send / retreive data.
 * 
 * @see fever.app.local.LocalisationListener
 * 
 * @author Romain Ecarnot
 */
class fvaswing.components.reader.abstract.FvPlayList extends EventBroadcaster 
	implements DragListener, LocalisationListener
{
	//-------------------------------------------------------------------------
	// Events definition
	//-------------------------------------------------------------------------
	
	/** Broadcasted when user double-click on file. */
	public static var onOpenFileEVENT : EventType = new EventType( "onOpenFile" );
	
	/** Broadcasted when user want to embed list in reader window. */
	public static var onEmbedListEVENT : EventType = new EventType( "onEmbedList" );
	
	/** Broadcasted when user close the list. */
	public static var onCloseListEVENT : EventType = new EventType( "onCloseList" );
	
	/** Broadcasted when user click on "open list" menubar item. */
	public static var onLoadListEVENT : EventType = new EventType( "onLoadList" );
	
	
	//-------------------------------------------------------------------------
	// Private properties
	//-------------------------------------------------------------------------
	
	private var _id : String;
	
	private var _frame : JFrame;
	private var _panel : JPanel;
	
	private var _scroll : JScrollPane;
	private var _tree : JListTree;
	private var _model : DefaultTreeModel;
	
	private var _titleLabel : JLabel;
	private var _titleInput : JLabel;
	private var _urlLabel : JLabel;
	private var _urlInput : JLabel;
		
	private var _rootNode : DefaultMutableTreeNode;
	private var _docNode : DefaultMutableTreeNode;
	
	private var _menubar : JMenuBar;
	private var _fileMenu : FvMenu;
	private var _openListItem : FvMenuItem;
	private var _embededList : FvMenuItem;
	private var _closeList : FvMenuItem;
	
	private var _docMap : Map;
	
	private var _resources : FvReaderResources;
	
	private var _enabled : Boolean;
	private var _enabledExternalFile : Boolean;
	
	private var _extNode : DefaultMutableTreeNode;

	private var _saveListItem : FvMenuItem;
	private var _clearListItem : JMenuItem;
	
	private var _extMap : Map;
	private var _name : String;
	
	private var _keyMap : ShortcutMap;
	
		
	//-------------------------------------------------------------------------
	// Public API
	//-------------------------------------------------------------------------
	
	/**
	 * Constructor.
	 * 
	 * @param name Playlist name
	 * @param enabledExternalLoading Allow user to load external file
	 */
	public function FvPlayList( name : String,  enabledExternalLoading : Boolean )
	{
		_resources = FvReaderResources.getInstance();
		Localisation.addLocalisationListener( this );
		
		_name = ( name.length > 0 ) ? name : ClassUtils.getClassName();
		_id = _name + HashCodeFactory.getKey( this );
		
		_docMap = new Map();
		_extMap = new Map();
		
		_enabledExternalFile = ( enabledExternalLoading != undefined )
			? enabledExternalLoading
			: false;
		
		_enabled = true;
		
		_keyMap = ShortcutLocator.getMap( HashCodeFactory.getKey( this ).toString() );
		_configure();
	}
	
	
	/**
	 * Returns identifier for DND support.
	 */
	public function getListID() : String
	{ 
		return _id;
	}
	
	/**
	 * Opens dialog.
	 */
	public function open( callPopUp : Boolean ) : Void
	{
		if( callPopUp )  _panel.append( getView(), BorderLayout.CENTER );
		
		onLocalisationUpdate();
		
		_frame.show();
		_frame.setActive( true );
	}
	
	/**
	 * Closes frame.
	 */
	public function close() : Void
	{
		_frame.tryToClose();
	}
	
	/**
	 * Returns main container.
	 */
	public function getView() : Component
	{
		return _tree;
	}
	
	/**
	 * Adds passed-in {@link fvaswing.components.reader.playlist.FvPlayListItem} 
	 * to list.
	 */
	public function addDocument( item : FvPlayListItem ) : Void
	{
		_addDocument( _docMap, _docNode, item );
	}
	
	/**
	 * Removes passed-in {@link fvaswing.components.reader.playlist.FvPlayListItem} 
	 * from list.
	 */
	public function removeDocument( item : FvPlayListItem ) : Void
	{
		_removeDocument( _docMap, _docNode, item );
	}
	
	/**
	 * Removes all elements in internal playlist.
	 */
	public function removeAllDocument() : Void
	{
		_removeAllDocument( _docMap, _docNode );	
	}
	
	/**
	 * Updates passed-in {@link fvaswing.components.reader.playlist.FvPlayListItem} 
	 * item informations.
	 */
	public function updateDocument( item : FvPlayListItem ) : Void
	{
		_updateInformations( item );
	}
	
	/**
	 * Adds passed-in {@link fvaswing.components.reader.playlist.FvPlayListItem} 
	 * to list.
	 * 
	 * <p>Item will be inserted in "external" tree node
	 */
	public function addExternalDocument( item : FvPlayListItem ) : Void
	{
		_addDocument( _extMap, _extNode, item );
	}
	
	/**
	 * Removes passed-in {@link fvaswing.components.reader.playlist.FvPlayListItem} 
	 * from list.
	 */
	public function removeExternalDocument( item : FvPlayListItem ) : Void
	{
		_removeDocument( _extMap, _extNode, item );
	}
	
	/**
	 * Save external document list into an application SharedObject
	 */
	public function save() : Void
	{
		if( !_enabled ) return;
		
		var it : Iterator = _extMap.getValuesIterator();
		var external : Array = new Array();
		
		while( it.hasNext() )
		{
			var node : FvPlayListNode = it.next();
			var item : FvPlayListItem = node.getItem();
			external.push( item.toObject() );
		}
		
		Fever.application.config.save( _name, external ); 
	}
	
	/**
	 * Clears external links.
	 */
	public function clear() : Void
	{
		if( !_enabled ) return;
		
		_removeAllDocument( _extMap, _extNode );
	}
	
	/**
	 * Triggered when Localisation language change.
	 */
	public function onLocalisationUpdate( event : LocalisationEvent ) : Void
	{
		_docNode.setUserObject( _resources.docFolderLabel );
		_model.reload( _docNode );
		
		_frame.setTitle( _resources.listTitle );
		_titleLabel.setText( _resources.titleLabel );
		_urlLabel.setText( _resources.urlLabel );
		
		_fileMenu.setText( _resources.fileMenuLabel );
		_embededList.setText( _resources.embdedLabel );
		_closeList.setText( _resources.closeLabel );
		
		if( _enabledExternalFile )
		{
			_openListItem.setText( _resources.openListLabel );
			_saveListItem.setText( _resources.saveListLabel );
			_clearListItem.setText( _resources.clearListLabel );	
		}
		
		_extNode.setUserObject( _resources.extFolderLabel );
		_model.reload( _extNode );
	}
	
	/**
	 * Avoid playlist actions if {@code enabled == false }
	 */
	public function setEnabled( enabled : Boolean ) : Void
	{
		_enabled = enabled;
	}
	
	/**
	 * Returns string representation.
	 */
	public function toString() : String
	{
		return Stringifier.parse( this );
	}
	
	
	//-------------------------------------------------------------------------
	// Private implementation
	//-------------------------------------------------------------------------
	
	private function _configure() : Void
	{
		_model = new DefaultTreeModel( _getTreeModel() );
		
		_tree = new JListTree(
			_model,
			new GeneralListCellFactory( DefaultListTreeCell, true, true, 20 )
		);
		_tree.setOpaque( true );
		_tree.setRootVisible( false );
		_tree.setDragEnabled( true );
//		_tree.setSourceDataDragName( _id );		_tree.addDragAcceptableInitiator( getView() ); //TODO test new DND feature
		_tree.setAutoDragAndDropType( JListTree.DND_COPY );
		_tree.addEventListener( JLabel.ON_DRAG_RECOGNIZED, _startDrag, this);
		_tree.addEventListener( JListTree.ON_ITEM_CLICKED, _handleOpenFile, this );
		
		DragManager.addDragListener( this );
		
		_scroll = new JScrollPane( _tree );
		
		_configureBasedFrame();
		
		_panel = new JPanel( new BorderLayout( 5, 5 ) );
		_panel.setBorder( new EmptyBorder( null, new Insets( 5,5,5,5 ) ) );
		_panel.append ( _createMenubar(), BorderLayout.NORTH );
		_panel.append ( _scroll, BorderLayout.CENTER );
		_panel.append ( _createInfoPanel(), BorderLayout.SOUTH );		
		_frame.setContentPane( _panel );
	}
	
	private function _getTreeModel() : MutableTreeNode
	{
        _rootNode = new DefaultMutableTreeNode("root");
        _docNode = new DefaultMutableTreeNode( _resources.docFolderLabel );        
        _rootNode.append( _docNode );
        
        if( _enabledExternalFile )
        {
        	 _extNode = new DefaultMutableTreeNode( _resources.extFolderLabel );
        	 _rootNode.append( _extNode );
        	 _loadExternalDocument();
        	 _model.reload( _extNode );
        }
        
        return _rootNode;
    }
    
    private function _loadExternalDocument() : Void
	{
		var external : Array = Fever.application.config.load( _name );
		var l : Number = external.length;
		
		for( var i : Number = 0; i < l; i++ )
			_addDocument( _extMap, _extNode, _buildNewItemInstance( external[i] ) );
	}
	
	private function _removeAllDocument( map : Map, node : DefaultMutableTreeNode ) : Void
	{
		map.clear();
		node.removeAllChildren();
		_model.reload( node );
	}
	
	/**
	 * Overrides for custom item type.
	 */
	private function _buildNewItemInstance( o : Object ) : FvPlayListItem
	{
		var item : FvPlayListItem = new FvPlayListItem( o.url, o.title );
		return item;
	}
	
	/**
	 * Configures the based {@code JFrame} container.
	 */
	private function _configureBasedFrame() : Void
	{
		_frame = new JFrame( FvAsWing.getInstance().getRootMovieClip(), _resources.listTitle, false );
		_frame.setResizable( true );
		_frame.setClosable( true );
		_frame.setDefaultCloseOperation( JFrame.HIDE_ON_CLOSE );
		_frame.addEventListener( JFrame.ON_WINDOW_CLOSING, _handleCloseList, this );
		_frame.addEventListener( JFrame.ON_WINDOW_ACTIVATED, Delegate.create( this, _toggleKeyMapping, true ) );
		_frame.addEventListener( JFrame.ON_WINDOW_DEACTIVATED, Delegate.create( this, _toggleKeyMapping, false ) );
		_frame.setSize( 400, 300 );
	}
	
	private function _toggleKeyMapping( source : JFrame, b : Boolean ) : Void
	{
		if( b ) _keyMap.load();
		else _keyMap.unload();	
	}
	
	/**
	 * Overrides to custommize item informations.
	 */
	private function _createInfoPanel() : JPanel
	{
		_titleLabel = _createLabel( _resources.titleLabel );
		_urlLabel = _createLabel( _resources.urlLabel );
		
		_titleInput = new JLabel( "-", JLabel.LEFT );
		_urlInput = new JLabel( "-", JLabel.LEFT );
		
		var p : JPanel = new JPanel( new SoftBoxLayout( SoftBoxLayout.Y_AXIS, 5 ) );
		p.append( _createLine( _titleLabel, _titleInput ) );
		p.append( _createLine( _urlLabel, _urlInput ) );
		return p;
	}
	
	private function _createLabel( caption : String ) : JLabel
	{
		var l : JLabel = new JLabel( caption, JLabel.LEFT );
		l.setPreferredWidth( 75 );
		l.setFont( new ASFont( null, null, true ) );
		return l;
	}
	
	private function _createLine( label : JLabel, input : JLabel ) : JPanel
	{
		var p : JPanel = new JPanel( new BorderLayout( 5,5 ) );
		p.append( label, BorderLayout.WEST );
		p.append( input, BorderLayout.CENTER );
		return p;
	}
	
	private function _addDocument( map : Map, parentNode : DefaultMutableTreeNode, item : FvPlayListItem ) : Void
	{
		if( !map.containsKey( item.getURL() ) )
		{
			var node : FvPlayListNode =  new FvPlayListNode( item );
			 
			map.put( item.getURL(), node );
			parentNode.append( node );
			_model.reload( parentNode );
		}
		else FeverDebug.ERROR( item.getURL() + " is already in list" );
	}
	
	private function _removeDocument( map : Map, parentNode : DefaultMutableTreeNode, item : FvPlayListItem ) : Void
	{
		if( map.containsKey( item.getURL() ) )
		{
			var node : FvPlayListNode = map.get( item.getURL() );
			
			map.remove( item.getURL() );
			parentNode.remove( node );
			_model.reload( parentNode );
		}	
	}
	
	private function _handleOpenFile( source : JListTree, value : TreePath, cell : TreeCell, clickCount : Number ) : Void
	{
		var node : FvPlayListNode = FvPlayListNode( value.getLastPathComponent() );
		var item : FvPlayListItem = node.getItem();
		
		if( node.isLeaf() )
		{
			if( clickCount == 2 )
			{
				broadcastEvent( new BasicEvent( onOpenFileEVENT, item ) );
			}
			else
			{
				_updateInformations( item );
			}
		}
	}
	
	/**
	 * Overrides for custom item.
	 */
	private function _updateInformations( item : FvPlayListItem ) : Void
	{
		_titleInput.setText( item.getTitle() );
		_urlInput.setText( item.getURL() );
	}
	
	
	//-------------------------------------------------------------------------
	// Menubar
	//-------------------------------------------------------------------------
	
	private function _addShortcut( component : Component, shortcut : KeyDefinition ) : Void
	{
		_keyMap.registerCustomType( FvAsWing.AWSHORTCUT, shortcut, component );
	}
	
	private function _createMenubar() : JMenuBar
	{
		_fileMenu = new FvMenu( _resources.fileMenuLabel );
		
		if( _enabledExternalFile )
		{
			_openListItem = _fileMenu.addMenuItem( _resources.openListLabel, _handleOpenList, this, new KeyCombo( Keyboard.onKeyCONTROL, Keyboard.onKeyL ) );
			_keyMap.registerCustomType( FvAsWing.AWSHORTCUT, _openListItem.getShortCut(), _openListItem );
			
			_fileMenu.append( new JSeparator( JSeparator.HORIZONTAL ) );
		}
		
		_embededList = _fileMenu.addMenuItem( _resources.embdedLabel, _handleEmbedList, this, new KeyCombo( Keyboard.onKeyCONTROL, Keyboard.onKeyB ) );
		_keyMap.registerCustomType( FvAsWing.AWSHORTCUT, _embededList.getShortCut(), _embededList );
		
		if( _enabledExternalFile )
		{
			_saveListItem = _fileMenu.addMenuItem( _resources.saveListLabel, _handleSaveList, this, new KeyCombo( Keyboard.onKeyCONTROL, Keyboard.onKeyS ) );
			_keyMap.registerCustomType( FvAsWing.AWSHORTCUT, _saveListItem.getShortCut(), _saveListItem );
		
			_clearListItem = _fileMenu.addMenuItem( _resources.clearListLabel, _handleClearList, this  );
		
			_fileMenu.append( new JSeparator( JSeparator.HORIZONTAL ) );
		}
		
		_closeList = _fileMenu.addMenuItem( _resources.closeLabel, _handleCloseList, this, new KeyCombo( Keyboard.onKeyCONTROL, Keyboard.onKeyW ) );
		_keyMap.registerCustomType( FvAsWing.AWSHORTCUT, _closeList.getShortCut(), _closeList );
		 
		_menubar = new JMenuBar();
		_menubar.append( _fileMenu );
		
		return _menubar;
	}
	
	private function _handleEmbedList( source : JMenuItem ) : Void
	{
		_frame.tryToClose();
		broadcastEvent( new BasicEvent( onEmbedListEVENT, this ) );
	}
	
	private function _handleOpenList( source : FvMenuItem ) : Void
	{
		broadcastEvent( new BasicEvent( onLoadListEVENT, this ) );
	}
	
	private function _handleCloseList( source : JMenuItem ) : Void
	{
		_frame.tryToClose();
		broadcastEvent( new BasicEvent( onCloseListEVENT, this ) );
	}
	
	private function _handleSaveList( source : JMenuItem ) : Void
	{
		if( _saveListItem.isEnabled() )	save();
	}
	
	private function _handleClearList( source : JMenuItem ) : Void
	{
		if( _clearListItem.isEnabled() ) clear();
	}
	
	//-------------------------------------------------------------------------
	// DND Callbacks
	//-------------------------------------------------------------------------
	
	private function _startDrag( dragInitiator : JListTree ,touchedChild : Component ) : Void
	{
		var node : FvPlayListNode = FvPlayListNode( dragInitiator.getSelectedValue().getLastPathComponent() );
		if( !node.isLeaf() ) 
		{
			DragManager.setDropMotion( new RejectedMotion() );
		}
		else FvCopyCursor.show();
	}
	
	public function onDragDrop(dragInitiator : Component, sourceData : SourceData, pos : Point, targetComponent : Component) : Void
	{
		FvCopyCursor.hide();
	}
	
	public function onDragStart(dragInitiator : Component, sourceData : SourceData, pos : Point) : Void {}
	public function onDragEnter(dragInitiator : Component, sourceData : SourceData, pos : Point, targetComponent : Component) : Void {}
	public function onDragOverring(dragInitiator : Component, sourceData : SourceData, pos : Point, targetComponent : Component) : Void {}
	public function onDragExit(dragInitiator : Component, sourceData : SourceData, pos : Point, targetComponent : Component) : Void {}

}